import { Stage } from 'babylon-lib/base/stage';
import { Mesh } from "@babylonjs/core/Meshes/mesh";
import { MeshBuilder } from '@babylonjs/core/Meshes/meshBuilder';
import { StandardMaterial } from '@babylonjs/core/Materials/standardMaterial';
import { Matrix, Vector3 } from '@babylonjs/core/Maths/math.vector';
import { Quaternion } from '@babylonjs/core/Maths/math.vector';
import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
import { Constants } from '@babylonjs/core/Engines/constants';
import { Texture } from '@babylonjs/core/Materials/Textures/texture';


class VirtualJoystick {

    private static _fromMesh: Mesh;
    private static _toMesh: Mesh;
    private static _active = false;
    private static _from?: Vector3;
    private static _to = new Vector3();
    private static _tv0 = new Vector3();
    private static _stage: Stage;
    private static _tv1 = new Vector3();
    private static _tv2 = new Vector3();
    private static _hidePointer = new Vector3(1e6, 1e6, 0.99);
    private static _initialized = false;

    public static initialize(stage: Stage): void {
        this._stage = stage;
        this._fromMesh = this.initializeFromMesh(stage);
        this._toMesh = this.initializeToMesh(stage);
        this._fromMesh.parent = stage.mainCamera;
        this._toMesh.parent = stage.mainCamera;
        this._fromMesh.isVisible = this._toMesh.isVisible = this._active;
        this._initialized = true;
    }

    public static get initialized(): boolean {
        return this._initialized;
    }

    public static set fromTexture(texture: Texture) {
        const mat = this._fromMesh.material as StandardMaterial;
        mat.opacityTexture = texture;
        mat.emissiveTexture = texture;
    }

    public static set toTexture(texture: Texture) {
        const mat = this._toMesh.material as StandardMaterial;
        mat.opacityTexture = texture;
        mat.emissiveTexture = texture;
    }

    public static set active(active: boolean) {
        if (active === this._active) return;
        this._active = active;
        this._fromMesh.isVisible = this._toMesh.isVisible = active;
        if (!active) {
            this.screenToWorld(this._hidePointer, this._fromMesh);
            this.screenToWorld(this._hidePointer, this._toMesh);
        }
    }

    public static update(x?: number, y?: number): void {
        if (x === undefined || y === undefined) {
            this._from = undefined;
        } else {
            if (!this._from) {
                this._from = this._tv0.set(x, y, 0);
            }
            this._to.set(x, y, 0);
        }
        if (!this._active || !this._from) return;
        this.screenToWorld(this._from!, this._fromMesh);
        this.screenToWorld(this._to, this._toMesh);
    }

    private static screenToWorld(pointer: Vector3, target: TransformNode): void {
        const scope = VirtualJoystick;
        const camera = scope._stage.mainCamera;
        const width = scope._stage.canvas.clientWidth;
        const height = scope._stage.canvas.clientHeight;
        scope._tv1.set(pointer.x, pointer.y, 0.99);
        const wld = new Matrix();
        Matrix.ComposeToRef(Vector3.OneReadOnly, camera.absoluteRotation, camera.globalPosition, wld);

        Vector3.UnprojectToRef(scope._tv1, width, height, Matrix.IdentityReadOnly, camera.getViewMatrix(), camera.getProjectionMatrix(), this._tv2);
        Vector3.TransformCoordinatesToRef(scope._tv2, wld.invert(), target.position);
    }

    private static initializeFromMesh(stage: Stage): Mesh {
        const m = MeshBuilder.CreatePlane('virtualJoystickFromMesh', { width: 1, height: 1 }, stage.mainScene);
        m.rotationQuaternion = Quaternion.FromEulerAngles(0, Math.PI, 0);
        m.isPickable = false;

        const mat = new StandardMaterial('virtualJoystickFromMesh-material', stage.mainScene);
        mat.disableLighting = true;
        mat.depthFunction = Constants.ALWAYS;

        m.material = mat;
        return m;
    }

    private static initializeToMesh(stage: Stage): Mesh {
        const m = MeshBuilder.CreatePlane('virtualJoystickToMesh', { width: 1, height: 1 }, stage.mainScene);
        m.rotationQuaternion = Quaternion.FromEulerAngles(0, Math.PI, 0);
        m.isPickable = false;

        const mat = new StandardMaterial('virtualJoystickToMesh-material', stage.mainScene);
        mat.disableLighting = true;
        mat.depthFunction = Constants.ALWAYS;

        m.material = mat;
        return m;
    }

}


export { VirtualJoystick };